package com.hksj.slm.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.hksj.cbm.entity.DeviceInstance;
import com.hksj.cbm.service.DeviceInstanceService;
import com.hksj.cbm.vo.DeriveMetadataVO;
import com.hksj.cbm.vo.RuleSceneVO;
import com.hksj.common.core.common.Constants;
import com.hksj.common.core.common.SpecialityModelCodeEnum;
import com.hksj.common.core.util.EasyExcelUtil;
import com.hksj.slm.dto.*;
import com.hksj.slm.mapper.StatisticAnalysisMapper;
import com.hksj.slm.vo.*;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author fjt
 * @data 2024/8/22 10:23
 * @describe 智能照明统计分析
 */
@Service
public class StatisticAnalysisService {


    @Autowired
    private StatisticAnalysisMapper statisticAnalysisMapper;
    @Autowired
    private DeviceInstanceService deviceInstanceService;



    /**
     * 设备概览
     * @param dto
     * @return
     */
    public SmartLightingDeviceOverviewVO deviceOverView(CockpitStatisticAnalysisDTO dto){
        List<DeviceInstance> deviceList = statisticAnalysisMapper.queryDevice(dto, SpecialityModelCodeEnum.smart_lighting.getCode());
        SmartLightingDeviceOverviewVO overviewVO = new SmartLightingDeviceOverviewVO();
        if (CollUtil.isEmpty(deviceList)){
            overviewVO.setOnlineRate(new BigDecimal(0.00));
            return overviewVO;
        }

        // 在线
        List<DeviceInstance> onLineDeviceList = deviceList.stream().
                filter(device -> "online".equals(device.getState()))
                .collect(Collectors.toList());
        // 离线
        List<DeviceInstance> offLineDeviceList = deviceList.stream().
                filter(device -> "offline".equals(device.getState()))
                .collect(Collectors.toList());

        // 未激活
        List<DeviceInstance> notActiveList = deviceList.stream().
                filter(device -> "notActive".equals(device.getState()))
                .collect(Collectors.toList());

        overviewVO.setTotal(deviceList.size());
        overviewVO.setOnlineNum(CollUtil.isNotEmpty(onLineDeviceList) ? onLineDeviceList.size() : 0);
        overviewVO.setOfflineNum(CollUtil.isNotEmpty(offLineDeviceList) ? offLineDeviceList.size() : 0);
        overviewVO.setNotActiveNum(CollUtil.isNotEmpty(notActiveList) ? notActiveList.size() : 0);
        // 占比
        if (CollUtil.isNotEmpty(onLineDeviceList)){
            BigDecimal onlineRate = new BigDecimal(onLineDeviceList.size()).divide(new BigDecimal(deviceList.size()),  5, BigDecimal.ROUND_HALF_UP);
            overviewVO.setOnlineRate(onlineRate.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP));
        }
        return overviewVO;
    }


    /**
     * 连续照明时长排名(前5)
     * @param dto
     * @return
     */
    public List<LightingDurationTopFiveVO>  lightingDurationRanking(CockpitStatisticAnalysisDTO dto){
        // 查询这些设备每个回路的照明时长并按时长倒序排名(取前5)
        dto.setCode(SpecialityModelCodeEnum.smart_lighting.getCode());
        List<LightingDurationVO> periodList = statisticAnalysisMapper.listTopFive(dto);
        if (CollUtil.isNotEmpty(periodList)){
            List<LightingDurationTopFiveVO> voList = new ArrayList<>();
            periodList.forEach( lightingDurationVO -> {
                StringBuilder builder = new StringBuilder();
                if (StringUtils.isNotBlank(lightingDurationVO.getBuildingName())){
                    builder.append(lightingDurationVO.getBuildingName());
                }
                if (StringUtils.isNotBlank(lightingDurationVO.getFloorName())){
                    builder.append(lightingDurationVO.getFloorName());
                }
                if (StringUtils.isNotBlank(lightingDurationVO.getRoomName())){
                    builder.append(lightingDurationVO.getRoomName());
                }
                if (StringUtils.isNotBlank(lightingDurationVO.getPointName())){
                    builder.append(lightingDurationVO.getPointName());
                }
                LightingDurationTopFiveVO vo = LightingDurationTopFiveVO.builder().pathName(builder.toString())
                        .duration(new BigDecimal(lightingDurationVO.getDurationLong()).divide(new BigDecimal(3600), 2, BigDecimal.ROUND_HALF_UP))
                        .build();
                voList.add(vo);
            });
            return voList;
        }
        return null;
    }


    /**
     * 定时控制策略查询
     * @return
     */
    public List<RuleSceneVO> queryScheduledTasks(String ruleType){
        return statisticAnalysisMapper.queryScheduledTasks(SpecialityModelCodeEnum.smart_lighting.getCode(), ruleType);
    }


    /**
     *
     * @param deviceIdList
     * @return 设备id-每个property的key : 每个key的中文名称
     */
    private Map<String, String> buildProperty(List<String> deviceIdList){
        // 设备id : <设备监测指标key : 指标相关信息>
        Map<String, Map<String, DeriveMetadataVO>> propertyMap = deviceInstanceService.batchQueryDeviceAttribute(deviceIdList);
        Map<String, String> propertyNameMap = new HashMap<>();
        for(Map.Entry<String, Map<String, DeriveMetadataVO>> entry : propertyMap.entrySet()){
            entry.getValue().forEach((k, v) -> {
                propertyNameMap.put(entry.getKey() + "-" + k, v.getKeyName());
            });
        }
        return propertyNameMap;
    }


    /**
     * 统计指定房间中各回路的照明时长
     * @param dto
     * @return
     */
    public LightingDurationGroupLoopVO queryLightingDurationDetail(DurationStatisticByRoomIdDTO dto){
        // 查询指定时间范围内的照明时长
        QueryLightingDurationDTO queryDto = new QueryLightingDurationDTO();
        BeanUtils.copyProperties(dto, queryDto);
        queryDto.setCode(SpecialityModelCodeEnum.smart_lighting.getCode());
        List<LightingDurationVO> durationVOList = statisticAnalysisMapper.queryLightingDurationDetail(queryDto);
        if (CollUtil.isEmpty(durationVOList)){
            return null;
        }

        // 查询每个设备中，每个property的中文名称
        List<String> deviceIdList = durationVOList.stream().map(LightingDurationVO::getDeviceId).distinct().collect(Collectors.toList());
        Map<String, String> propertyMap = buildProperty(deviceIdList);

        /** 整理durationVOList，计算符合要求的照明时长，
         *      如：查询时间是8到9点整，则每个连续的照明时长最多只有8到9点的这一个小时，超出的部分不属于当前查询时间范围内的照明时长(和产品确认过)
         */
        Date queryStartTime = dto.getStartTime();
        Date queryEndTime = dto.getEndTime();
        durationVOList.forEach(lightingDurationVO -> {
            // 计算开始时间
            Date startTime = null;
            // 计算截止时间
            Date endTime = null;
            Date openTime = lightingDurationVO.getOpenTime();
            Date closeTime = lightingDurationVO.getCloseTime();
            if (queryStartTime.compareTo(openTime) > 0){
                // 查询起始时间晚于openTime
                startTime = queryStartTime;
            }else {
                startTime = openTime;
            }

            if (ObjectUtil.isNotNull(closeTime) && queryEndTime.compareTo(closeTime) > 0){
                // 查询截止时间晚于closeTime
                endTime = closeTime;
            }else {
                Date now = com.hksj.common.core.util.DateUtil.getNow();
                if (queryEndTime.compareTo(now) > 0){
                    // 查询截止时间晚于当前系统时间
                    endTime = now;
                }else {
                    endTime = queryEndTime;
                }
            }
            Long durationLong = (endTime.getTime() - startTime.getTime()) / 1000;
            lightingDurationVO.setDurationLong(durationLong);
            lightingDurationVO.setPropertyName(propertyMap.get(lightingDurationVO.getDeviceId() + "-" + lightingDurationVO.getProperty()));
        });

        // 统计每支回路的照明时长(分组求和)
        Map<String, List<LightingDurationVO>> map = durationVOList.stream().collect(Collectors.groupingBy(p -> p.getDeviceId() + "-" + p.getProperty()));
        List<LightingDurationVO> list = new ArrayList<>(map.size());
        for (Map.Entry<String, List<LightingDurationVO>> entry : map.entrySet()){
            LightingDurationVO durationVO = entry.getValue().get(0);
            LightingDurationVO vo = LightingDurationVO.builder().buildingId(durationVO.getBuildingId()).buildingName(durationVO.getBuildingName())
                    .floorId(durationVO.getFloorId()).floorName(durationVO.getFloorName()).departmentItemValue(durationVO.getDepartmentItemValue())
                    .departmentItemValueName(durationVO.getDepartmentItemValueName()).roomId(durationVO.getRoomId()).roomName(durationVO.getRoomName())
                    .pointId(durationVO.getPointId()).pointName(durationVO.getPointName()).deviceId(durationVO.getDeviceId()).deviceName(durationVO.getDeviceName())
                    .property(durationVO.getProperty()).propertyName(durationVO.getPropertyName()).build();
            // 求和
            Long lightingDuration = entry.getValue().stream().mapToLong(LightingDurationVO::getDurationLong).sum();
            vo.setDuration(new BigDecimal(lightingDuration).divide(new BigDecimal(3600), 2, BigDecimal.ROUND_HALF_UP));
            list.add(vo);
        }

        // 按时长倒序查询
        list = list.stream().sorted(Comparator.comparing(LightingDurationVO::getDuration, Comparator.reverseOrder())).collect(Collectors.toList());
        StringBuilder builder = new StringBuilder(list.get(0).getBuildingName()).append("-").append(list.get(0).getFloorName()).append("-").append(list.get(0).getRoomName());
        return LightingDurationGroupLoopVO.builder().tableName(builder.toString()).list(list).build();
    }


    /**
     * 导出指定房间中各回路的照明时长
     * @param response
     * @param dto
     */
    public void exportList(HttpServletResponse response, DurationStatisticByRoomIdDTO dto){
        LightingDurationGroupLoopVO loopVO = queryLightingDurationDetail(dto);
        if (ObjectUtil.isNotNull(loopVO)){
            String fileName = new StringBuffer().append(loopVO.getTableName()).append("累计照明时长明细")
                    .append(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss")).append(".xlsx").toString();
            EasyExcelUtil.exportExcelData(response, fileName, loopVO.getList(), LightingDurationVO.class);
        }else {
            throw new RuntimeException("当前房间没有符合要求的数据，无需导出");
        }
    }

    /**
     * 按房间统计指定楼层或科室的累计照明时长
     * @param dto
     * @return
     */
    public List<LightingDurationGroupRoomVO> queryLightingDurationGroupRoom(DurationStatisticByFloorIdDTO dto){
        Long floorId = dto.getFloorId();
        String departmentItemValue = dto.getDepartmentItemValue();
        Boolean blean = (ObjectUtil.isNull(floorId) && StringUtils.isBlank(departmentItemValue))
                || (ObjectUtil.isNotNull(floorId) && StringUtils.isNotBlank(departmentItemValue));
        if (blean){
            throw new RuntimeException("楼层id和科室字典明细值只能有一个不为空");
        }

        // 查询指定时间范围内的照明时长
        QueryLightingDurationDTO queryDto = new QueryLightingDurationDTO();
        BeanUtils.copyProperties(dto, queryDto);
        return calculationDurationGroupRoom(queryDto);
    }


    /**
     * 根据查询时间校正计算照明时长的起止时间，
     * 如：查询时间是8到9点整，则每个连续的照明时长最多只有8到9点的这一个小时，超出的部分不属于当前查询时间范围内的照明时长，不参与照明时长的计算(和产品确认过)
     * @param durationVO
     * @param queryStartTime
     * @param queryEndTime
     * @return
     */
    private void checkLightingTime(LightingDurationVO durationVO, Date queryStartTime, Date queryEndTime){
        // 计算开始时间
        Date startTime = null;
        Date openTime = durationVO.getOpenTime();
        Date closeTime = durationVO.getCloseTime();
        if (queryStartTime.compareTo(openTime) > 0){
            // 查询起始时间晚于openTime
            startTime = queryStartTime;
        }else {
            startTime = openTime;
        }
        durationVO.setOpenTime(startTime);

        // 计算截止时间
        Date endTime = null;
        if (ObjectUtil.isNotNull(closeTime) && queryEndTime.compareTo(closeTime) > 0){
            // 查询截止时间晚于closeTime
            endTime = closeTime;
        }else {
            Date now = com.hksj.common.core.util.DateUtil.getNow();
            if (queryEndTime.compareTo(now) > 0){
                // 查询截止时间晚于当前系统时间
                endTime = now;
            }else {
                endTime = queryEndTime;
            }
        }
        durationVO.setCloseTime(endTime);
    }


    /**
     * 统计每个房间的累计照明时长
     * @return
     */
    private List<LightingDurationGroupRoomVO> calculationDurationGroupRoom(QueryLightingDurationDTO queryDto){
        queryDto.setCode(SpecialityModelCodeEnum.smart_lighting.getCode());
        List<LightingDurationVO> durationVOList = statisticAnalysisMapper.queryLightingDurationDetail(queryDto);
        if (CollUtil.isEmpty(durationVOList)){
            return null;
        }
        // 根据查询时间校正计算照明时长的起止时间
        durationVOList.forEach(lightingDurationVO -> {
            checkLightingTime(lightingDurationVO, queryDto.getStartTime(), queryDto.getEndTime());
        });

        // 同一房间的回路分组
        List<Long> roomIdList = durationVOList.stream().map(LightingDurationVO::getRoomId).distinct().collect(Collectors.toList());
        if (roomIdList.stream().anyMatch(x -> x == null)){
            throw new RuntimeException("配置错误！智能照明的回路设备应配置到具体的房间或和房间同级别的走廊道等位置");
        }
        Map<Long, List<LightingDurationVO>> map = durationVOList.stream().collect(Collectors.groupingBy(LightingDurationVO::getRoomId));

        /**
         * 计算每个房间的累计照明时长(同一房间多个回路的，注意照明时间是否有重叠的部分，有重叠的，重叠部分的时间只能算一次)
         * 如：A房间有1,2两个回路，回路1从8点开启，9点关闭；回路2从8点半开启，9点半关闭。此时，A房间的累计照明时长应该是1个半小时，而不是2小时(两回路各自时长的相加)
         */
        List<LightingDurationGroupRoomVO> list = new ArrayList<>(map.size());
        map.forEach((k, v) -> {
            LightingDurationVO durationVO = v.get(0);
            // 同一个房间只会属于同一个楼层或科室
            LightingDurationGroupRoomVO roomVO = LightingDurationGroupRoomVO.builder().floorId(durationVO.getFloorId()).floorName(durationVO.getFloorName())
                    .departmentItemValue(durationVO.getDepartmentItemValue()).departmentItemValueName(durationVO.getDepartmentItemValueName())
                    .roomId(k).roomName(durationVO.getRoomName()).build();
            roomVO.setDuration(calculationDuration(v));
            list.add(roomVO);
        });
        return list;
    }


    /**
     * 计算每个房间的累计照明时长(同一房间多个回路的，注意照明时间是否有重叠的部分，有重叠的，重叠部分的时间只能算一次)
     * @param list  同一房间内若干回路在查询时间范围内有效的连续照明时间段
     * @return 时长(单位：小时)
     */
    private BigDecimal calculationDuration(List<LightingDurationVO> list){
        // 按开启时间升序排序
        list = list.stream().sorted(Comparator.comparing(LightingDurationVO::getOpenTime, Comparator.naturalOrder())).collect(Collectors.toList());

        Long duation = list.get(0).getCloseTime().getTime() - list.get(0).getOpenTime().getTime();
        // 已计算过时长的最大时间
        Date max = list.get(0).getCloseTime();

        for(int i = 1; i < list.size(); i++){
            Date openTime = list.get(i).getOpenTime();
            Date closeTime = list.get(i).getCloseTime();

            // 完全重叠，丢弃。如：i-1是8点10点，i是9点到10点
            if (openTime.compareTo(list.get(i-1).getOpenTime()) >= 0 && closeTime.compareTo(list.get(i-1).getCloseTime()) <= 0){
                continue;
            }
            // 部分重叠且i有超出的部分：i的开启时间大于等于i-1的开启时间，且i的关闭时间大于i-1的关闭时间
            if (openTime.compareTo(list.get(i-1).getOpenTime()) >= 0 && closeTime.compareTo(list.get(i-1).getCloseTime()) > 0){
                // 重叠的部分在i-1中已经累加，此处只累加超出的部分
                duation = closeTime.getTime() - max.getTime() + duation;
            }

            // 没有重叠
            if (openTime.compareTo(list.get(i-1).getCloseTime()) >= 0){
                duation = closeTime.getTime() - openTime.getTime() + duation;
            }
            // 当前最大关闭时间为i的关闭时间
            max = closeTime;
        }
        // 毫秒转小时
        return new BigDecimal(duation).divide(new BigDecimal(1000 * 3600), 2, BigDecimal.ROUND_HALF_UP);
    }


    /**
     * 按楼层或科室统计指定楼栋的累计照明时长(楼层或科室所属的每个房间的累计照明时长之和)
     * @param dto
     * @return
     */
    public List<LightingDurationGroupFloorVO> queryLightingDurationGroupFloor(DurationStatisticByBuildingIdDTO dto){
        QueryLightingDurationDTO queryDto = new QueryLightingDurationDTO();
        BeanUtils.copyProperties(dto, queryDto);
        // 统计每个房间的累计照明时长
        List<LightingDurationGroupRoomVO> roomList = calculationDurationGroupRoom(queryDto);
        if (CollUtil.isEmpty(roomList)){
            return null;
        }

        List<LightingDurationGroupFloorVO> list = new ArrayList<>();
        // 统计方式(0-按楼层统计;1-按科室统计)
        if (Constants.ZERO.equals(dto.getType())){
            Map<Long, List<LightingDurationGroupRoomVO>> map = roomList.stream().collect(Collectors.groupingBy(LightingDurationGroupRoomVO::getFloorId));
            map.forEach((k, v) -> {
                BigDecimal floorLightingDuration = v.stream().map(LightingDurationGroupRoomVO::getDuration).reduce(BigDecimal.ZERO, BigDecimal::add);
                LightingDurationGroupFloorVO vo = LightingDurationGroupFloorVO.builder().floorId(k)
                        .floorName(v.get(0).getFloorName())
                        .departmentItemValue(v.get(0).getDepartmentItemValue())
                        .departmentItemValueName(v.get(0).getDepartmentItemValueName())
                        .duration(floorLightingDuration).build();
                list.add(vo);
            });
        }

        if (Constants.ONE.equals(dto.getType())){
            List<String> departmentItemValueList = roomList.stream().map(LightingDurationGroupRoomVO::getDepartmentItemValue).distinct().collect(Collectors.toList());
            if (departmentItemValueList.stream().anyMatch(x -> StringUtils.isBlank(x))){
                throw new RuntimeException("科室信息未配置");
            }
            Map<String, List<LightingDurationGroupRoomVO>> map = roomList.stream().collect(Collectors.groupingBy(LightingDurationGroupRoomVO::getDepartmentItemValue));
            map.forEach((k, v) -> {
                BigDecimal floorLightingDuration = v.stream().map(LightingDurationGroupRoomVO::getDuration).reduce(BigDecimal.ZERO, BigDecimal::add);
                LightingDurationGroupFloorVO vo = LightingDurationGroupFloorVO.builder().departmentItemValue(k)
                        .floorId(v.get(0).getFloorId())
                        .floorName(v.get(0).getFloorName())
                        .departmentItemValueName(v.get(0).getDepartmentItemValueName())
                        .duration(floorLightingDuration).build();
                list.add(vo);
            });
        }
        return list.stream().sorted(Comparator.comparing(LightingDurationGroupFloorVO::getDuration, Comparator.reverseOrder())).collect(Collectors.toList());
    }

}






